/*
 * XMPPClientHelper.java
 *
 * Created on March 23, 2007, 1:21 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.xeerkat.restlet.xmpp;


import java.io.Reader;
import java.util.logging.Level;
import org.infoset.xml.DocumentLoader;
import org.restlet.Client;

import org.infoset.xml.Element;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.engine.ClientHelper;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.xeerkat.xmpp.ID;
import org.xeerkat.xmpp.Protocol;

/**
 *
 * @author alex
 */
public class XMPPClientHelper  extends ClientHelper {
   DocumentLoader loader;
    /**
     * Constructor.
     * 
     * @param client
     *            The client to help.
     */
    public XMPPClientHelper(Client client) {
        super(client);
        getProtocols().add(XMPP.XMPP_PROTOCOL);
        loader = new SAXDocumentLoader();
    }

    protected void generateMessage(Form parameters,ItemConstructor constructor,ItemDestination stanza)
       throws XMLException
    {
       String subject = parameters.getFirstValue("subject");
       if (subject!=null) {
          stanza.send(constructor.createElement(Protocol.SUBJECT));
          stanza.send(constructor.createCharacters(subject));
          stanza.send(constructor.createElementEnd(Protocol.SUBJECT));
       }
       String body = parameters.getFirstValue("body");
       if (body!=null) {
          stanza.send(constructor.createElement(Protocol.BODY));
          stanza.send(constructor.createCharacters(body));
          stanza.send(constructor.createElementEnd(Protocol.BODY));
       }
       String thread = parameters.getFirstValue("thread");
       if (thread!=null) {
          stanza.send(constructor.createElement(Protocol.THREAD));
          stanza.send(constructor.createCharacters(body));
          stanza.send(constructor.createElementEnd(Protocol.THREAD));
       }
    }

    
   /**
    * Handles a call.
    *
    * @param request
    *            The request to handle.
    * @param response
    *            The response to update.
    */
   public void handle(Request request, Response response) {
      Reference ref = request.getResourceRef();
      //System.out.println("Parsing: "+ref.toString());
      String sender = null;
      if (ref.isHierarchical()) {
         sender = ref.getAuthority().replace("%40", "@");
      }
      String path = ref.getPath();
      if (getLogger().isLoggable(Level.FINE)) {
         getLogger().fine("Raw path: " + path);
      }
      if (sender!=null && (path==null || path.length()==0 || path.charAt(0)!='/')) {
         response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         response.setEntity(new StringRepresentation("No recipient was specified in the URI.", MediaType.TEXT_PLAIN));
         return;
      }
      if (path.length()>0 && path.charAt(0)=='/') {
         path = path.substring(1);
      }
      final String recipient = path.replace("%40", "@");

      ID senderId = (ID)request.getAttributes().get("org.xeerkat.restlet.xmpp.from.id");
      if (senderId==null && sender!=null) {
         senderId = new ID(sender);
      }
      Context context = getHelped().getContext();
      XMPP xmpp = context==null ? null : (XMPP)context.getAttributes().get(XMPP.XMPP_ATTR);
      final XMPP.Identity identity = xmpp==null ? null : senderId == null ? xmpp.getDefaultIdentity() : xmpp.getIdentity(senderId);
      if (identity == null) {
         response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         response.setEntity(new StringRepresentation("No identity could be found for sender " + sender, MediaType.TEXT_PLAIN));
         return;
      }
      if (!identity.isConnected()) {
         response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
         response.setEntity(new StringRepresentation("The identity " + identity.getId() + " is not connected.", MediaType.TEXT_PLAIN));
         return;
      }
      String queryType = null;
      Form parameters = null;
      String query = ref.getQuery();
      if (query != null) {
         int semicolon = query.indexOf(";");
         if (semicolon == 0) {
            response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            response.setEntity(new StringRepresentation("The query is malformed: " + query));
            return;
         } else if (semicolon>0) {
            queryType = query.substring(0, semicolon);
            parameters = new Form(query.substring(semicolon + 1));
         } else {
            queryType = query;
         }
      }
      if (parameters == null) {
         parameters = new Form();
      }
      if (request.getMethod().equals(Method.GET) || (request.getMethod().equals(Method.POST) && !request.isEntityAvailable())) {
         if (queryType.equals("message")) {
            ItemConstructor constructor = identity.connection.getItemConstructor();
            ItemDestination stanza = identity.connection.getStanzaDestination();
            try {
               Element textMessage = constructor.createElement(Protocol.MESSAGE);
               textMessage.setAttributeValue("to", recipient);
               String id = parameters.getFirstValue("id");
               if (id != null) {
                  textMessage.setAttributeValue("id", id);
               }
               String type = parameters.getFirstValue("type");
               if (type != null) {
                  textMessage.setAttributeValue("type", type);
               }
               stanza.send(textMessage);
               generateMessage(parameters, constructor, stanza);
               stanza.send(constructor.createElementEnd(Protocol.MESSAGE));
            } catch (Exception ex) {
               response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
               response.setEntity(new StringRepresentation("Error while sending stanza: " + ex.getMessage(), MediaType.TEXT_PLAIN));
               return;
            }
         } else if (queryType.equals("disco")) {
            ItemConstructor constructor = identity.connection.getItemConstructor();
            ItemDestination stanza = identity.connection.getStanzaDestination();
            try {
               Element iq = constructor.createElement(Protocol.IQ);
               iq.setAttributeValue("to", recipient);
               String id = parameters.getFirstValue("id");
               if (id != null) {
                  iq.setAttributeValue("id", id);
               }
               String type = parameters.getFirstValue("type");
               if (type != null) {
                  iq.setAttributeValue("type", type);
               }
               String requestType = parameters.getFirstValue("request");
               String node = parameters.getFirstValue("node");
               if (request==null || requestType.equals("info")) {
                  stanza.send(iq);
                  Element disco = constructor.createElement(Protocol.DISCO_INFO_QUERY);
                  disco.addNamespaceBinding(Name.NO_PREFIX, Protocol.DISCO_INFO_QUERY.getNamespaceName());
                  if (node!=null) {
                     disco.setAttributeValue("node",node);
                  }
                  stanza.send(disco);
                  stanza.send(constructor.createElementEnd(Protocol.DISCO_INFO_QUERY));
               } else if (requestType.equals("items")) {
                  stanza.send(iq);
                  Element disco = constructor.createElement(Protocol.DISCO_ITEMS_QUERY);
                  disco.addNamespaceBinding(Name.NO_PREFIX, Protocol.DISCO_INFO_QUERY.getNamespaceName());
                  if (node!=null) {
                     disco.setAttributeValue("node",node);
                  }
                  stanza.send(disco);
                  stanza.send(constructor.createElementEnd(Protocol.DISCO_ITEMS_QUERY));
               } else {
                  response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
                  response.setEntity(new StringRepresentation("Unrecognized request parameter value: " + requestType, MediaType.TEXT_PLAIN));
                  return;
               }
               stanza.send(constructor.createElementEnd(Protocol.IQ));
            } catch (Exception ex) {
               response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
               response.setEntity(new StringRepresentation("Error while sending stanza: " + ex.getMessage(), MediaType.TEXT_PLAIN));
               return;
            }
         } else {
            response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            response.setEntity(new StringRepresentation("Unable to turn get request into a stanza due to unrecognized query type " + queryType));
            return;

         }
      } else if (request.getMethod().equals(Method.POST)) {
         Representation message = request.getEntity();
         if (MediaType.APPLICATION_XML.equals(message.getMediaType()) || MediaType.TEXT_XML.equals(message.getMediaType()) || MediaType.APPLICATION_ALL_XML.includes(message.getMediaType())) {
            if (queryType == null) {
               try {
                  Reader reader = message.getReader();
                  loader.generate(reader, new ItemDestination() {
                     ItemDestination stanza = identity.connection.getStanzaDestination();
                     int level = 0;
                     public void send(Item item)
                        throws XMLException
                     {
                        switch (item.getType()) {
                           case DocumentItem:
                           case DocumentEndItem:
                              break;
                           case ElementItem:
                              if (level==0) {
                                 Element e = (Element)item;
                                 if (e.getAttributeValue("to")==null) {
                                    e.setAttributeValue("to", recipient);
                                 }
                              }
                              level++;
                              stanza.send(item);
                              break;
                           case ElementEndItem:
                              level--;
                           default:
                              stanza.send(item);
                       }
                     }
                  });
                  reader.close();
               } catch (Exception ex) {
                  response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
                  response.setEntity(new StringRepresentation("Error while sending stanza: " + ex.getMessage(), MediaType.TEXT_PLAIN));
                  return;
               }
            } else if (queryType.equals("message")) {
               ItemConstructor constructor = identity.connection.getItemConstructor();
               ItemDestination stanza = identity.connection.getStanzaDestination();
               try {
                  Element textMessage = constructor.createElement(Protocol.MESSAGE);
                  textMessage.setAttributeValue("to", recipient);
                  String id = parameters.getFirstValue("id");
                  if (id != null) {
                     textMessage.setAttributeValue("id", id);
                  }
                  String type = parameters.getFirstValue("type");
                  if (type != null) {
                     textMessage.setAttributeValue("type", type);
                  }
                  stanza.send(textMessage);
                  generateMessage(parameters, constructor, stanza);
                  Reader reader = message.getReader();
                  loader.generate(reader, stanza);
                  reader.close();
                  stanza.send(constructor.createElementEnd(Protocol.MESSAGE));
               } catch (Exception ex) {
                  response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
                  response.setEntity(new StringRepresentation("Error while sending stanza: " + ex.getMessage(), MediaType.TEXT_PLAIN));
                  return;
               }

            } else {
               response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               response.setEntity(new StringRepresentation("Unable to turn media type " + message.getMediaType() + " into a stanza due to unrecognized query type " + queryType));
               return;
            }
         } else if (MediaType.TEXT_ALL.includes(message.getMediaType())) {
            if (!queryType.equals("message")) {
               response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               response.setEntity(new StringRepresentation("Unable to turn media type " + message.getMediaType() + " into a stanza due to unrecognized query type " + queryType));
               return;
            }
            ItemConstructor constructor = identity.connection.getItemConstructor();
            ItemDestination stanza = identity.connection.getStanzaDestination();

            try {
               Element textMessage = constructor.createElement(Protocol.MESSAGE);
               textMessage.setAttributeValue("to", recipient);
               stanza.send(textMessage);
               stanza.send(constructor.createElement(Protocol.BODY));
               stanza.send(constructor.createCharacters(message.getText()));
               stanza.send(constructor.createElementEnd(Protocol.BODY));
               stanza.send(constructor.createElementEnd(Protocol.MESSAGE));
            } catch (Exception ex) {
               response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
               response.setEntity(new StringRepresentation("Error while sending stanza: " + ex.getMessage(), MediaType.TEXT_PLAIN));
               return;
            }
         } else {
            response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            response.setEntity(new StringRepresentation("Unable to turn media type " + message.getMediaType() + " into a stanza."));
            return;
         }
      } else {
         response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
         response.setEntity(new StringRepresentation("Unsupported method: " + request.getMethod(), MediaType.TEXT_PLAIN));
      }
   }

   void checkForClose(XMPP.Identity identity, Exception ex) {
      if (ex instanceof java.net.SocketException) {
         // we're closed;
         identity.failed();
      } else {
         Throwable t = ex.getCause();
         if (t instanceof Exception) {
            checkForClose(identity, (Exception) t);
         }
      }
   }

}
